import requests
from requests import RequestException, ReadTimeout, Timeout
from requests.adapters import HTTPAdapter

# 各种请求方式
#   r = requests.get('https://api.github.com/events')
#   r = requests.post('http://httpbin.org/post', data = {'key':'value'})
#   r = requests.put('http://httpbin.org/put', data = {'key':'value'})
#   r = requests.delete('http://httpbin.org/delete')
#   r = requests.head('http://httpbin.org/get')
#   r = requests.options('http://httpbin.org/get')
print('------------------------- GET 请求 --------------------------')
# 1. get 请求
# response = requests.get('https://www.hao123.com/')
# print('get 请求：', response.text)

# 2. 自定义请求头
# headers = {
#     # 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
#     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0',
# }
# response = requests.get('https://www.baidu.com/s?wd=python&pn=1', headers)
# print('自定义请求头的 get 请求：', response.text)

# 3. url编码（中文、特殊字符）
# from urllib.parse import urlencode
#
# encode_res = urlencode({'k': 'egon'}, encoding='utf-8')
# print(encode_res)
# keyword = encode_res.split('=')[1]
# print(keyword)
# # 然后拼接成url
# url = 'https://www.baidu.com/s?wd=%s&pn=1' % keyword
# headers = {
#     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0',
# }
# response = requests.get('https://www.baidu.com/s?wd=python&pn=1', headers)
# print('url编码的 get 请求：', response.text)

# 4. params参数
# response = requests.get('https://www.baidu.com/s', params={'wd': 'python'})

# 5. cookies
# Cookies = {'user_session': 'wGMHFJKgDcmRIVvcA14_Wrt_3xaUyJNsBnPbYzEL6L0bHcfc', }
# response = requests.get('https://www.baidu.com/s', cookies=Cookies)

print('------------------------- Session --------------------------')
# Session作用
#   连接池复用 : 复用 TCP 连接，减少频繁握手开销，提升性能（尤其高并发场景）；
#   统一配置 : 全局设置 Headers、Proxies、超时时间等参数，避免重复代码;
#   Cookie 自动管理 ：自动保存和发送 Cookie，模拟浏览器会话（如登录状态保持）；
#   跨请求持久化数据 ： 在多次请求间共享数据（如身份认证 Token）

# TCP 连接池 :
#   默认行为：Session 使用 urllib3 的连接池，默认最大保持 10 个连接，支持 HTTP/1.1 的 Keep-Alive。
#   性能提升：避免重复三次握手，减少延迟（尤其对高频请求）。
# 创建 Session
session = requests.Session()

# 全局配置
session.headers.update({
    'User-Agent': 'Mozilla/5.0',
    'Authorization': 'Bearer xyz123'
})

# 自动携带配置的 Headers 和 Cookies
response = session.get('https://api.example.com/data')

# 自动管理登录状态
# 登录（自动保存 Cookie）
login_resp = session.post('https://example.com/login', data={'user': 'admin', 'pass': '123'})
# 后续请求自动携带登录后的 Cookie
profile_resp = session.get('https://example.com/profile')

'''
与 requests.get() 的直接对比
    场景              requests.get()                              Session()
    单次请求           简单，无需管理会话                             略繁琐，适合多次请求
    多次请求性能	      每次新建连接，性能较低	                     连接池复用，性能高
    Cookie 处理	      手动管理	                                 自动跨请求携带
    配置复用	          需每个请求单独设置参数	                     全局配置一次
'''

print('------------------------- POST 请求 --------------------------')
'''
一 目标站点分析
    浏览器输入https://github.com/login
    然后输入错误的账号密码，抓包
    发现登录行为是post提交到：https://github.com/session
    而且请求头包含cookie
    而且请求体包含：
        commit:Sign in
        utf8:✓
        authenticity_token:lbI8IJCwGslZS8qJPnof5e7ZkCoSoMn6jmDTsL1r/m06NLyIbw7vCrpwrFAPzHMep3Tmf/TSJVoXWrvDZaVwxQ==
        login:egonlin
        password:123
二 流程分析
    先GET：https://github.com/login拿到初始cookie与authenticity_token
    返回POST：https://github.com/session， 带上初始cookie，带上请求体（authenticity_token，用户名，密码等）
    最后拿到登录cookie
    ps：如果密码时密文形式，则可以先输错账号，输对密码，然后到浏览器中拿到加密后的密码，github的密码是明文
'''

import re

# 第一次请求
r1 = requests.get('https://github.com/login')
r1_cookie = r1.cookies.get_dict()  # 拿到初始cookie(未被授权)
authenticity_token = re.findall(r'name="authenticity_token".*?value="(.*?)"', r1.text)[0]  # 从页面中拿到CSRF TOKEN

# 第二次请求：带着初始cookie和TOKEN发送POST请求给登录页面，带上账号密码
data = {
    'commit': 'Sign in',
    'utf8': '✓',
    'authenticity_token': authenticity_token,
    'login': '2324576765@qq.com',
    'password': 'alex3714'
}
r2 = requests.post('https://github.com/session', data=data, cookies=r1_cookie)
login_cookie = r2.cookies.get_dict()

# 第三次请求：以后的登录，拿着login_cookie就可以,比如访问一些个人配置
r3 = requests.get('https://github.com/settings/emails', cookies=login_cookie)

print('2324576765@qq.com' in r3.text)  # True

# 使用session 代替 requests
# requests.session()自动帮我们保存cookie信息
session = requests.session()
# 第一次请求
r1 = session.get('https://github.com/login')
authenticity_token = re.findall(r'name="authenticity_token".*?value="(.*?)"', r1.text)[0]  # 从页面中拿到CSRF TOKEN

# 第二次请求
data = {
    'commit': 'Sign in',
    'utf8': '✓',
    'authenticity_token': authenticity_token,
    'login': '2324576765@qq.com',
    'password': 'alex3714'
}
r2 = session.post('https://github.com/session', data=data, )

# 第三次请求
r3 = requests.get('https://github.com/settings/emails')

print('317828332@qq.com' in r3.text)  # True

requests.session()  # 自动帮我们保存cookie信息

print('------------------------- Response --------------------------')
respone = requests.get('http://www.jianshu.com')
# respone属性
print(respone.text)
print(respone.content)

print(respone.status_code)
print(respone.headers)
print(respone.cookies)
print(respone.cookies.get_dict())
print(respone.cookies.items())

print(respone.url)
print(respone.history)

print(respone.encoding)

# 响应流处理
from contextlib import closing

with closing(requests.get('xxx', stream=True)) as response:
    for line in response.iter_content():
        pass

# 响应编码问题
response = requests.get('http://www.autohome.com/news')
response.encoding = 'gbk'  # 汽车之家网站返回的页面内容为gb2312编码的，而requests的默认编码为ISO-8859-1，如果不设置成gbk则中文乱码
print(response.text)

# 获取二进制数据
response = requests.get(
    'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1509868306530&di=712e4ef3ab258b36e9f4b48e85a81c9d&imgtype=0&src=http%3A%2F%2Fc.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F11385343fbf2b211e1fb58a1c08065380dd78e0c.jpg')
with open('a.jpg', 'wb') as f:
    f.write(response.content)

# 解析json
import json

response = requests.get('http://httpbin.org/get')
res1 = json.loads(response.text)  # 太麻烦
res2 = response.json()  # 直接获取json数据
print(res1 == res2)  # True

# 转发与历史
# 第一次请求
r1 = requests.get('https://github.com/login')
r1_cookie = r1.cookies.get_dict()  # 拿到初始cookie(未被授权)
authenticity_token = re.findall(r'name="authenticity_token".*?value="(.*?)"', r1.text)[0]  # 从页面中拿到CSRF TOKEN

# 第二次请求：带着初始cookie和TOKEN发送POST请求给登录页面，带上账号密码
data = {
    'commit': 'Sign in',
    'utf8': '✓',
    'authenticity_token': authenticity_token,
    'login': '317828332@qq.com',
    'password': 'alex3714'
}

# 测试一：没有指定allow_redirects=False,则响应头中出现Location就跳转到新页面，r2代表新页面的response
r2 = requests.post('https://github.com/session', data=data, cookies=r1_cookie)

print(r2.status_code)  # 200
print(r2.url)  # 看到的是跳转后的页面
print(r2.history)  # 看到的是跳转前的response
print(r2.history[0].text)  # 看到的是跳转前的response.text

# 测试二：指定allow_redirects=False,则响应头中即便出现Location也不会跳转到新页面，r2代表的仍然是老页面的response
r2 = requests.post('https://github.com/session', data=data, cookies=r1_cookie, allow_redirects=False)

print(r2.status_code)  # 302
print(r2.url)  # 看到的是跳转前的页面https://github.com/session
print(r2.history)  # []

print('------------------------- 高级用法 --------------------------')
# SSL(https)
# 证书验证(大部分网站都是https)
respone = requests.get('https://www.12306.cn')  # 如果是ssl请求,首先检查证书是否合法,不合法则报错,程序终端

# 改进1:去掉报错,但是会报警告
respone = requests.get('https://www.12306.cn', verify=False)  # 不验证证书,报警告,返回200

# 改进2:去掉报错,并且去掉警报信息
from requests.packages import urllib3

urllib3.disable_warnings()  # 关闭警告
respone = requests.get('https://www.12306.cn', verify=False)
print(respone.status_code)

# 改进3:加上证书
respone = requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key'))

# 使用代理 ，支持socks代理
proxies = {
    'https': 'https://183.214.79.102:9999',  # 带用户名密码的代理,@符号前是用户名与密码
    'http': 'http://localhost:9743',
    'https': 'https://localhost:9743',
    'http': 'socks5://user:pass@host:port',  # socks代理
    'https': 'socks5://user:pass@host:port'  # socks代理
}
respone = requests.get('https://www.12306.cn', proxies=proxies)

print(respone.status_code)

# 超时设置
respone = requests.get('https://www.baidu.com', timeout=0.0001)

# 认证设置
# 认证设置:登陆网站是,弹出一个框,要求你输入用户名密码（与alter很类似），此时是无法获取html的
# 但本质原理是拼接成请求头发送
#         r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
# 一般的网站都不用默认的加密方式，都是自己写
# 那么我们就需要按照网站的加密方式，自己写一个类似于_basic_auth_str的方法
# 得到加密字符串后添加到请求头
#         r.headers['Authorization'] =func('.....')

# 看一看默认的加密方式吧，通常网站都不会用默认的加密设置
import requests
from requests.auth import HTTPBasicAuth

r = requests.get('xxx', auth=HTTPBasicAuth('user', 'password'))
print(r.status_code)

# HTTPBasicAuth可以简写为如下格式
r = requests.get('xxx', auth=('user', 'password'))
print(r.status_code)

# 异常处理
try:
    r = requests.get('http://www.baidu.com', timeout=0.00001)
except ReadTimeout:
    print('===:')
except ConnectionError:  # 网络不通
    print('-----')
except Timeout:
    print('aaaaa')
except RequestException:
    print('Error')

# 上传文件
files = {'file': open('a.jpg', 'rb')}
respone = requests.post('http://httpbin.org/post', files=files)
print(respone.status_code)

# Handler
# 创建 Session 并配置全局参数
session = requests.Session()
session.headers.update({'User-Agent': 'MyApp/1.0'})
# 添加自定义 Adapter（类似 Handler）
adapter = HTTPAdapter(
    max_retries=3,  # 最大重试次数
    pool_connections=10  # 连接池大小
)
session.mount('http://', adapter)  # 绑定到 HTTP 协议
session.mount('https://', adapter)  # 绑定到 HTTPS 协议
# 发送请求（自动应用配置）
response = session.get('https://api.example.com/data')


# 自定义 Adapter
class CustomAdapter(requests.adapters.HTTPAdapter):
    def send(self, request, **kwargs):
        # 请求前处理（如签名）
        request.headers['X-Auth'] = ''
        # 调用父类方法发送请求
        response = super().send(request, **kwargs)

        # 响应后处理（如重试）
        if response.status_code == 401:
            request.headers['X-Auth'] = ''
            response = super().send(request, **kwargs)
        return response


# 使用自定义 Adapter
session = requests.Session()
session.mount('https://', CustomAdapter())


# 钩子
# 定义钩子函数
def log_request(response, *args, **kwargs):
    print(f"Requested {response.url} with status {response.status_code}")


# 注册钩子
session = requests.Session()
session.hooks['response'] = [log_request]

# 触发钩子
session.get('https://httpbin.org/get')
